/* *
 *
 *  (c) 2009-2019 Øystein Moseng
 *
 *  Code for sonifying single points.
 *
 *  License: www.highcharts.com/license
 *
 *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
 *
 * */
"use strict";
import H from "../../parts/Globals.js";
import U from "../../parts/Utilities.js";
var pick = U.pick;
/**
 * Define the parameter mapping for an instrument.
 *
 * @requires module:modules/sonification
 *
 * @interface Highcharts.PointInstrumentMappingObject
 */ /**
 * Define the volume of the instrument. This can be a string with a data
 * property name, e.g. `'y'`, in which case this data property is used to define
 * the volume relative to the `y`-values of the other points. A higher `y` value
 * would then result in a higher volume. This option can also be a fixed number
 * or a function. If it is a function, this function is called in regular
 * intervals while the note is playing. It receives three arguments: The point,
 * the dataExtremes, and the current relative time - where 0 is the beginning of
 * the note and 1 is the end. The function should return the volume of the note
 * as a number between 0 and 1.
 * @name Highcharts.PointInstrumentMappingObject#volume
 * @type {string|number|Function}
 */ /**
 * Define the duration of the notes for this instrument. This can be a string
 * with a data property name, e.g. `'y'`, in which case this data property is
 * used to define the duration relative to the `y`-values of the other points. A
 * higher `y` value would then result in a longer duration. This option can also
 * be a fixed number or a function. If it is a function, this function is called
 * once before the note starts playing, and should return the duration in
 * milliseconds. It receives two arguments: The point, and the dataExtremes.
 * @name Highcharts.PointInstrumentMappingObject#duration
 * @type {string|number|Function}
 */ /**
 * Define the panning of the instrument. This can be a string with a data
 * property name, e.g. `'x'`, in which case this data property is used to define
 * the panning relative to the `x`-values of the other points. A higher `x`
 * value would then result in a higher panning value (panned further to the
 * right). This option can also be a fixed number or a function. If it is a
 * function, this function is called in regular intervals while the note is
 * playing. It receives three arguments: The point, the dataExtremes, and the
 * current relative time - where 0 is the beginning of the note and 1 is the
 * end. The function should return the panning of the note as a number between
 * -1 and 1.
 * @name Highcharts.PointInstrumentMappingObject#pan
 * @type {string|number|Function|undefined}
 */ /**
 * Define the frequency of the instrument. This can be a string with a data
 * property name, e.g. `'y'`, in which case this data property is used to define
 * the frequency relative to the `y`-values of the other points. A higher `y`
 * value would then result in a higher frequency. This option can also be a
 * fixed number or a function. If it is a function, this function is called in
 * regular intervals while the note is playing. It receives three arguments:
 * The point, the dataExtremes, and the current relative time - where 0 is the
 * beginning of the note and 1 is the end. The function should return the
 * frequency of the note as a number (in Hz).
 * @name Highcharts.PointInstrumentMappingObject#frequency
 * @type {string|number|Function}
 */
/**
 * @requires module:modules/sonification
 *
 * @interface Highcharts.PointInstrumentOptionsObject
 */ /**
 * The minimum duration for a note when using a data property for duration. Can
 * be overridden by using either a fixed number or a function for
 * instrumentMapping.duration. Defaults to 20.
 * @name Highcharts.PointInstrumentOptionsObject#minDuration
 * @type {number|undefined}
 */ /**
 * The maximum duration for a note when using a data property for duration. Can
 * be overridden by using either a fixed number or a function for
 * instrumentMapping.duration. Defaults to 2000.
 * @name Highcharts.PointInstrumentOptionsObject#maxDuration
 * @type {number|undefined}
 */ /**
 * The minimum pan value for a note when using a data property for panning. Can
 * be overridden by using either a fixed number or a function for
 * instrumentMapping.pan. Defaults to -1 (fully left).
 * @name Highcharts.PointInstrumentOptionsObject#minPan
 * @type {number|undefined}
 */ /**
 * The maximum pan value for a note when using a data property for panning. Can
 * be overridden by using either a fixed number or a function for
 * instrumentMapping.pan. Defaults to 1 (fully right).
 * @name Highcharts.PointInstrumentOptionsObject#maxPan
 * @type {number|undefined}
 */ /**
 * The minimum volume for a note when using a data property for volume. Can be
 * overridden by using either a fixed number or a function for
 * instrumentMapping.volume. Defaults to 0.1.
 * @name Highcharts.PointInstrumentOptionsObject#minVolume
 * @type {number|undefined}
 */ /**
 * The maximum volume for a note when using a data property for volume. Can be
 * overridden by using either a fixed number or a function for
 * instrumentMapping.volume. Defaults to 1.
 * @name Highcharts.PointInstrumentOptionsObject#maxVolume
 * @type {number|undefined}
 */ /**
 * The minimum frequency for a note when using a data property for frequency.
 * Can be overridden by using either a fixed number or a function for
 * instrumentMapping.frequency. Defaults to 220.
 * @name Highcharts.PointInstrumentOptionsObject#minFrequency
 * @type {number|undefined}
 */ /**
 * The maximum frequency for a note when using a data property for frequency.
 * Can be overridden by using either a fixed number or a function for
 * instrumentMapping.frequency. Defaults to 2200.
 * @name Highcharts.PointInstrumentOptionsObject#maxFrequency
 * @type {number|undefined}
 */
/**
 * An instrument definition for a point, specifying the instrument to play and
 * how to play it.
 *
 * @interface Highcharts.PointInstrumentObject
 */ /**
 * An Instrument instance or the name of the instrument in the
 * Highcharts.sonification.instruments map.
 * @name Highcharts.PointInstrumentObject#instrument
 * @type {Highcharts.Instrument|string}
 */ /**
 * Mapping of instrument parameters for this instrument.
 * @name Highcharts.PointInstrumentObject#instrumentMapping
 * @type {Highcharts.PointInstrumentMappingObject}
 */ /**
 * Options for this instrument.
 * @name Highcharts.PointInstrumentObject#instrumentOptions
 * @type {Highcharts.PointInstrumentOptionsObject|undefined}
 */ /**
 * Callback to call when the instrument has stopped playing.
 * @name Highcharts.PointInstrumentObject#onEnd
 * @type {Function|undefined}
 */
/**
 * Options for sonifying a point.
 * @interface Highcharts.PointSonifyOptionsObject
 */ /**
 * The instrument definitions for this point.
 * @name Highcharts.PointSonifyOptionsObject#instruments
 * @type {Array<Highcharts.PointInstrumentObject>}
 */ /**
 * Optionally provide the minimum/maximum values for the points. If this is not
 * supplied, it is calculated from the points in the chart on demand. This
 * option is supplied in the following format, as a map of point data properties
 * to objects with min/max values:
 *  ```js
 *      dataExtremes: {
 *          y: {
 *              min: 0,
 *              max: 100
 *          },
 *          z: {
 *              min: -10,
 *              max: 10
 *          }
 *          // Properties used and not provided are calculated on demand
 *      }
 *  ```
 * @name Highcharts.PointSonifyOptionsObject#dataExtremes
 * @type {object|undefined}
 */ /**
 * Callback called when the sonification has finished.
 * @name Highcharts.PointSonifyOptionsObject#onEnd
 * @type {Function|undefined}
 */
import utilities from "./utilities.js";
// Defaults for the instrument options
// NOTE: Also change defaults in Highcharts.PointInstrumentOptionsObject if
//       making changes here.
var defaultInstrumentOptions = {
  minDuration: 20,
  maxDuration: 2000,
  minVolume: 0.1,
  maxVolume: 1,
  minPan: -1,
  maxPan: 1,
  minFrequency: 220,
  maxFrequency: 2200,
};
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
 * Sonify a single point.
 *
 * @sample highcharts/sonification/point-basic/
 *         Click on points to sonify
 * @sample highcharts/sonification/point-advanced/
 *         Sonify bubbles
 *
 * @requires module:modules/sonification
 *
 * @function Highcharts.Point#sonify
 *
 * @param {Highcharts.PointSonifyOptionsObject} options
 * Options for the sonification of the point.
 *
 * @return {void}
 */
function pointSonify(options) {
  var point = this,
    chart = point.series.chart,
    dataExtremes = options.dataExtremes || {},
    // Get the value to pass to instrument.play from the mapping value
    // passed in.
    getMappingValue = function (value, makeFunction, allowedExtremes) {
      // Function. Return new function if we try to use callback,
      // otherwise call it now and return result.
      if (typeof value === "function") {
        return makeFunction
          ? function (time) {
              return value(point, dataExtremes, time);
            }
          : value(point, dataExtremes);
      }
      // String, this is a data prop.
      if (typeof value === "string") {
        // Find data extremes if we don't have them
        dataExtremes[value] =
          dataExtremes[value] ||
          utilities.calculateDataExtremes(point.series.chart, value);
        // Find the value
        return utilities.virtualAxisTranslate(
          pick(point[value], point.options[value]),
          dataExtremes[value],
          allowedExtremes
        );
      }
      // Fixed number or something else weird, just use that
      return value;
    };
  // Register playing point on chart
  chart.sonification.currentlyPlayingPoint = point;
  // Keep track of instruments playing
  point.sonification = point.sonification || {};
  point.sonification.instrumentsPlaying =
    point.sonification.instrumentsPlaying || {};
  // Register signal handler for the point
  var signalHandler = (point.sonification.signalHandler =
    point.sonification.signalHandler || new utilities.SignalHandler(["onEnd"]));
  signalHandler.clearSignalCallbacks();
  signalHandler.registerSignalCallbacks({ onEnd: options.onEnd });
  // If we have a null point or invisible point, just return
  if (point.isNull || !point.visible || !point.series.visible) {
    signalHandler.emitSignal("onEnd");
    return;
  }
  // Go through instruments and play them
  options.instruments.forEach(function (instrumentDefinition) {
    var instrument =
        typeof instrumentDefinition.instrument === "string"
          ? H.sonification.instruments[instrumentDefinition.instrument]
          : instrumentDefinition.instrument,
      mapping = instrumentDefinition.instrumentMapping || {},
      extremes = H.merge(
        defaultInstrumentOptions,
        instrumentDefinition.instrumentOptions
      ),
      id = instrument.id,
      onEnd = function (cancelled) {
        // Instrument on end
        if (instrumentDefinition.onEnd) {
          instrumentDefinition.onEnd.apply(this, arguments);
        }
        // Remove currently playing point reference on chart
        if (chart.sonification && chart.sonification.currentlyPlayingPoint) {
          delete chart.sonification.currentlyPlayingPoint;
        }
        // Remove reference from instruments playing
        if (point.sonification && point.sonification.instrumentsPlaying) {
          delete point.sonification.instrumentsPlaying[id];
          // This was the last instrument?
          if (!Object.keys(point.sonification.instrumentsPlaying).length) {
            signalHandler.emitSignal("onEnd", cancelled);
          }
        }
      };
    // Play the note on the instrument
    if (instrument && instrument.play) {
      point.sonification.instrumentsPlaying[instrument.id] = instrument;
      instrument.play({
        frequency: getMappingValue(mapping.frequency, true, {
          min: extremes.minFrequency,
          max: extremes.maxFrequency,
        }),
        duration: getMappingValue(mapping.duration, false, {
          min: extremes.minDuration,
          max: extremes.maxDuration,
        }),
        pan: getMappingValue(mapping.pan, true, {
          min: extremes.minPan,
          max: extremes.maxPan,
        }),
        volume: getMappingValue(mapping.volume, true, {
          min: extremes.minVolume,
          max: extremes.maxVolume,
        }),
        onEnd: onEnd,
        minFrequency: extremes.minFrequency,
        maxFrequency: extremes.maxFrequency,
      });
    } else {
      H.error(30);
    }
  });
}
/**
 * Cancel sonification of a point. Calls onEnd functions.
 *
 * @requires module:modules/sonification
 *
 * @function Highcharts.Point#cancelSonify
 *
 * @param {boolean} [fadeOut=false]
 *        Whether or not to fade out as we stop. If false, the points are
 *        cancelled synchronously.
 *
 * @return {void}
 */
function pointCancelSonify(fadeOut) {
  var playing = this.sonification && this.sonification.instrumentsPlaying,
    instrIds = playing && Object.keys(playing);
  if (instrIds && instrIds.length) {
    instrIds.forEach(function (instr) {
      playing[instr].stop(!fadeOut, null, "cancelled");
    });
    this.sonification.instrumentsPlaying = {};
    this.sonification.signalHandler.emitSignal("onEnd", "cancelled");
  }
}
var pointSonifyFunctions = {
  pointSonify: pointSonify,
  pointCancelSonify: pointCancelSonify,
};
export default pointSonifyFunctions;
